import SEO from "../components/SEO";
import { TOC, TOCList, TOCLink } from "../components/TOC";
import { AsPropWarning } from "../components/AsPropWarning";
import { Pipe } from "../components/Pipe";

<SEO title="Accordion" description="Accessible accordion component for React" />

# Accordion

<TOC>
	<TOCList>
		<TOCLink href="#accordion-1">Accordion</TOCLink>
		<TOCLink href="#accordionitem">AccordionItem</TOCLink>
		<TOCLink href="#accordionbutton">AccordionButton</TOCLink>
		<TOCLink href="#accordionpanel">AccordionPanel</TOCLink>
		<TOCLink href="#useaccordioncontext">useAccordionContext</TOCLink>
		<TOCLink href="#useaccordionitemcontext">useAccordionItemContext</TOCLink>
	</TOCList>
</TOC>

- Source: https://github.com/reach/reach-ui/tree/main/packages/accordion
- WAI-ARIA: https://www.w3.org/TR/wai-aria-practices-1.2/#accordion

An accordion is a vertically stacked group of collapsible sections. An accordion is composed of grouped buttons and panels. When a user selects an accordion button, its corresponding panel should switch between 'open' and 'collapsed' states.

Accordions follow many consistent patterns but do allow for some variability in behavior. For example, some accordions only allow one panel to be open at a time, where others may allow multiple or all panels to be open simultaneously. Similarly, many accordions will allow all panels to be simultaneously collapsed, while others may require one panel to be open at all times.

If you are familiar with the disclosure pattern, an accordion will feel very similar. The key distinction is that a disclosure is a standalone component that consists of a single button-panel-group. Because of this, you cannot navigate between different disclosures with a keyboard the same way you can with an accordion. To provide users with a predictable behavior between components, it is important to keep disclosures and accordions visually distinct across your app.

## Installation

From the command line in your project directory, run `npm install @reach/accordion` or `yarn add @reach/accordion`. Then import the components and styles that you need:

```bash
npm install @reach/accordion
# or
yarn add @reach/accordion
```

```js
import {
	Accordion,
	AccordionItem,
	AccordionButton,
	AccordionPanel,
} from "@reach/accordion";
import "@reach/accordion/styles.css";
```

## Usage

```jsx
// jsx-demo
function Example() {
	return (
		<Accordion>
			<AccordionItem>
				<h3>
					<AccordionButton>Step 1: Do a thing</AccordionButton>
				</h3>
				<AccordionPanel>
					Here are some detailed instructions about doing a thing. I am very
					complex and probably contain a lot of content, so a user can hide or
					show me by clicking the button above.
				</AccordionPanel>
			</AccordionItem>
			<AccordionItem>
				<h3>
					<AccordionButton>Step 2: Do another thing</AccordionButton>
				</h3>
				<AccordionPanel>
					Here are some detailed instructions about doing yet another thing.
					There are a lot of things someone might want to do, so I am only going
					to talk about doing that other thing. I'll let my fellow accordion
					items go into detail about even more things.
				</AccordionPanel>
			</AccordionItem>
		</Accordion>
	);
}
```

### Accordion Headings

With most accordion components, the `AccordionPanel` is treated as a semantic `region` of the document, similar to an HTML `section` or `main` tag. By default, we assign `role="region"` and to each `AccordionPanel`, along with `aria-labelledby` referencing the associated `AccordionButton` component.

To improve the semantics of the markup further, the ARIA guidelines dictate that each accordion item's button should be wrapped in an element with `role="heading"`, or more simply, and HTML heading tag. Because headings are necessarily dependent on the context of their surrounding content, we do not wrap the `AccordionButton` inside of a heading tag by default. It is up to each developer to implement this detail in a way that makes sense for their application.

You can abstract a solution in a variety of ways. Perhaps you write an `AccordionHeader` wrapper component that accepts a `headingLevel` prop:

```jsx
function AccordionHeader({ headingLevel = 2, props }) {
	let Comp = "h" + headingLevel;
	return (
		<Comp>
			<AccordionButton {...props} />
		</Comp>
	);
}
```

You can also create a context-aware heading component so that the heading level increments appropriately as content is nested:

```jsx
// jsx-demo
(() => {
	const HeadingContext = createContext(2);

	function MyAccordionSection(props) {
		return (
			<div>
				<Heading>How to do a thing</Heading>
				<p>
					Below I am going to explain how you might do a thing, in two very
					important steps.
				</p>
				<Accordion>
					<ContextAwareAccordionItem>
						<AccordionHeader>Step 1: Do a thing</AccordionHeader>
						<AccordionPanel>
							Here are some detailed instructions about doing a thing. I am very
							complex and probably contain a lot of content, so a user can hide
							or show me by clicking the button above.
						</AccordionPanel>
					</ContextAwareAccordionItem>
					<AccordionItem>
						<AccordionHeader>Step 2: Do another thing</AccordionHeader>
						<AccordionPanel>
							Here are some detailed instructions about doing yet another thing.
							There are a lot of things someone might want to do, so I am only
							going to talk about doing that other thing. I'll let my fellow
							accordion items go into detail about even more things.
						</AccordionPanel>
					</AccordionItem>
				</Accordion>
			</div>
		);
	}

	function AccordionHeader(props) {
		return (
			<Heading>
				<AccordionButton {...props} />
			</Heading>
		);
	}

	function ContextAwareAccordionItem(props) {
		return (
			<Section>
				<AccordionItem {...props} />
			</Section>
		);
	}

	function Heading(props) {
		let Comp = "h" + Math.min(useContext(HeadingContext), 6);
		return <Comp {...props} />;
	}

	function Section(props) {
		let headingLevel = useContext(HeadingContext);
		return (
			<HeadingContext.Provider value={headingLevel + 1}>
				{props.children}
			</HeadingContext.Provider>
		);
	}

	return <MyAccordionSection />;
})();
```

## Component API

### Accordion

The wrapper component for all other accordion components. Each accordion component will consist of accordion items whose buttons are keyboard navigable using arrow keys.

```jsx
<Accordion index={index} onChange={(value) => setIndex(value)}>
	<AccordionItem>
		<h3>
			<AccordionButton>Step 1: Do a thing</AccordionButton>
		</h3>
		<AccordionPanel>...</AccordionPanel>
	</AccordionItem>
	<AccordionItem>
		<h3>
			<AccordionButton>Step 2: Do another thing</AccordionButton>
		</h3>
		<AccordionPanel>...</AccordionPanel>
	</AccordionItem>
</Accordion>
```

#### Accordion CSS Selectors

Please see the [styling guide](/styling).

```css
[data-reach-accordion] {
}
```

#### `collapsible` and `multiple` props

You can use the `collapsible` prop to dictate that an accordion should allow all panels to be collapsed simultaneously. By default, one panel must be in an `open` state at all times.

```jsx
// jsx-demo
function Example() {
	return (
		<Accordion collapsible>
			<AccordionItem>
				<h3>
					<AccordionButton>Step 1: Do a thing</AccordionButton>
				</h3>
				<AccordionPanel>
					Integer ad iaculis semper aenean nibh quisque hac eget volutpat, at
					dui sem accumsan cras congue mi varius egestas interdum, molestie
					blandit sociosqu sodales diam metus erat venenatis.
				</AccordionPanel>
			</AccordionItem>
			<AccordionItem>
				<h3>
					<AccordionButton>Step 2: Do another thing</AccordionButton>
				</h3>
				<AccordionPanel>
					Hendrerit faucibus litora justo aliquet inceptos gravida felis vel
					aenean, natoque fermentum nostra tempus ornare nam diam est, neque
					risus aliquam sapien vestibulum sociis integer eros.
				</AccordionPanel>
			</AccordionItem>
		</Accordion>
	);
}
```

The `multiple` prop dictates that any number of panels may be open at the same time. By default, when a user opens a new accordion item, the previously open item will be set to `collapsed`.

```jsx
// jsx-demo
function Example() {
	return (
		<Accordion multiple>
			<AccordionItem>
				<h3>
					<AccordionButton>Step 1: Do a thing</AccordionButton>
				</h3>
				<AccordionPanel>
					Integer ad iaculis semper aenean nibh quisque hac eget volutpat, at
					dui sem accumsan cras congue mi varius egestas interdum, molestie
					blandit sociosqu sodales diam metus erat venenatis.
				</AccordionPanel>
			</AccordionItem>
			<AccordionItem>
				<h3>
					<AccordionButton>Step 2: Do another thing</AccordionButton>
				</h3>
				<AccordionPanel>
					Hendrerit faucibus litora justo aliquet inceptos gravida felis vel
					aenean, natoque fermentum nostra tempus ornare nam diam est, neque
					risus aliquam sapien vestibulum sociis integer eros.
				</AccordionPanel>
			</AccordionItem>
		</Accordion>
	);
}
```

Using both props together dictates that any number of panels in an accordion can be `open` or `collapsed` at any time without regard to the state of other accordion items. These props are only relevant for uncontrolled accordion components, as the state of controlled accordion items is determined only by the `index` prop.

```jsx
// jsx-demo
function Example() {
	return (
		<Accordion collapsible multiple>
			<AccordionItem>
				<h3>
					<AccordionButton>Step 1: Do a thing</AccordionButton>
				</h3>
				<AccordionPanel>
					Integer ad iaculis semper aenean nibh quisque hac eget volutpat, at
					dui sem accumsan cras congue mi varius egestas interdum, molestie
					blandit sociosqu sodales diam metus erat venenatis.
				</AccordionPanel>
			</AccordionItem>
			<AccordionItem>
				<h3>
					<AccordionButton>Step 2: Do another thing</AccordionButton>
				</h3>
				<AccordionPanel>
					Hendrerit faucibus litora justo aliquet inceptos gravida felis vel
					aenean, natoque fermentum nostra tempus ornare nam diam est, neque
					risus aliquam sapien vestibulum sociis integer eros.
				</AccordionPanel>
			</AccordionItem>
		</Accordion>
	);
}
```

#### Controlled Accordion

If you want to control the accordion's open panels, you can do so by passing [`index`](#accordion-index) and [`onChange`](#accordion-onchange) props. The index corresponds with the order of each accordion item as they appear within an accordion. The index value passed sets its corresponding panel to an `open` state.

```jsx
const [index, setIndex] = React.useState(0);
return (
	<Accordion index={index} onChange={(value) => setIndex(value)}>
		<AccordionItem {...items[0].props} />
		<AccordionItem {...items[1].props} />
		<AccordionItem {...items[2].props} />
	</Accordion>
);
```

In a controlled accordion, multiple items can be set to `open` by passing an array of indices to the `index` prop.

```jsx
const [indices, setIndices] = React.useState([0, 2]);
function toggleItem(toggledIndex) {
	if (indices.includes(toggledIndex)) {
		setIndices(indices.filter((currentIndex) => currentIndex !== toggledIndex));
	} else {
		setIndices([...indices, toggledIndex].sort());
	}
}

return (
	<Accordion index={indices} onChange={toggleItem}>
		<AccordionItem {...items[0].props} />
		<AccordionItem {...items[1].props} />
		<AccordionItem {...items[2].props} />
	</Accordion>
);
```

#### Accordion Props

| Prop                                      | Type                          | Required |
| ----------------------------------------- | ----------------------------- | -------- |
| [`as`](#accordion-as)                     | `string` <Pipe /> `Component` | false    |
| [`children`](#accordion-children)         | `node`                        | true     |
| [`collapsible`](#accordion-collapsible)   | `boolean`                     | false    |
| [`defaultIndex`](#accordion-defaultindex) | `number` <Pipe /> `number[]`  | false    |
| [`index`](#accordion-index)               | `number` <Pipe /> `number[]`  | false    |
| [`multiple`](#accordion-multiple)         | `boolean`                     | false    |
| [`onChange`](#accordion-onchange)         | `func`                        | false    |
| [`readOnly`](#accordion-readonly)         | `boolean`                     | false    |

##### Accordion `as`

`as?: keyof JSX.IntrinsicElements | React.ComponentType`

A string representing an HTML element or a React component that will tell the `Accordion` what element to render. Defaults to `div`.

<AsPropWarning />

##### Accordion `children`

`children: React.ReactNode`

`Accordion` can accept `AccordionItem` components as children.

```jsx
<Accordion>
	<AccordionItem>
		<AccordionButton />
		<AccordionPanel />
	</AccordionItem>
	<AccordionItem>
		<AccordionButton />
		<AccordionPanel />
	</AccordionItem>
</Accordion>
```

##### Accordion `collapsible`

`collapsible?: boolean`

Whether or not all panels of an uncontrolled accordion can be toggled to a closed state. See the [`collapsible` and `multiple` props](#collapsible-and-multiple-props) section for details. Defaults to `false`.

##### Accordion `defaultIndex`

`defaultIndex?: number | number[]`

A default value for the open panel's index or indices in an uncontrolled accordion component when it is initially rendered.

```jsx
<Accordion defaultIndex={1}>
	<AccordionItem>
		<AccordionButton />
		<AccordionPanel>I will be closed on the initial render!</AccordionPanel>
	</AccordionItem>
	<AccordionItem>
		<AccordionButton />
		<AccordionPanel>I will be open on the initial render!</AccordionPanel>
	</AccordionItem>
</Accordion>
```

If an accordion has no defaultIndex, the initially rendered open panel depends on the `collapsible` prop.

- If `collapsible` is set to `true`, without a `defaultIndex` no panels will initially be open. Otherwise, the first panel at index `0` will initially be open.

You can only pass an array of indices to `defaultIndex` if you also set `multiple` to true.

```jsx
<Accordion defaultIndex={[0, 1]} multiple>
	<AccordionItem>
		<AccordionButton />
		<AccordionPanel>I will be open on the initial render!</AccordionPanel>
	</AccordionItem>
	<AccordionItem>
		<AccordionButton />
		<AccordionPanel>I will also be open on the initial render!</AccordionPanel>
	</AccordionItem>
</Accordion>
```

##### Accordion `index`

`index?: number | number[]`

The index or array of indices for open accordion panels. The `index` prop should be used along with `onChange` to create controlled accordion components.

```jsx
const [indices, setIndices] = React.useState([0, 2]);
function toggleItem(toggledIndex) {
	if (indices.includes(toggledIndex)) {
		setIndices(indices.filter((currentIndex) => currentIndex !== toggledIndex));
	} else {
		setIndices([...indices, toggledIndex].sort());
	}
}

return (
	<Accordion index={indices} onChange={toggleItem}>
		<AccordionItem {...items[0].props} />
		<AccordionItem {...items[1].props} />
		<AccordionItem {...items[2].props} />
	</Accordion>
);
```

##### Accordion `multiple`

`multiple?: boolean`

Whether or not multiple panels in an uncontrolled accordion can be opened at the same time. See the [`collapsible` and `multiple` props](#collapsible-and-multiple-props) section for details. Defaults to `false`.

##### Accordion `onChange`

`onChange?: (value: number) => void`

The callback that is fired when an accordion item's open state is changed.

##### Accordion `readOnly`

`readOnly?: boolean`

Whether or not an uncontrolled accordion is read-only (meaning that the user cannot toggle its state with a normal interaction). Defaults to `false`.

Generally speaking, you probably want to avoid this, as it can be confusing especially when navigating by keyboard. However, this may be useful if you want to lock an accordion under certain conditions (perhaps user authentication is required to access the content). In these instances, you may want to include an alert when a user tries to activate a read-only accordion panel to let them know why it does not toggle as may be expected.

### AccordionItem

A group that wraps an accordion's button and panel components.

#### AccordionItem CSS Selectors

Please see the [styling guide](/styling).

```css
[data-reach-accordion-item] {
	/* styles for all accordion items */
}
[data-reach-accordion-item][data-state="open"] {
	/* styles for all open accordion items */
}
[data-reach-accordion-item][data-state="collapsed"] {
	/* styles for all collapsed accordion items */
}
[data-reach-accordion-item][data-disabled] {
	/* styles for all disabled accordion items */
}
[data-reach-accordion-item][data-read-only] {
	/* styles for all read-only accordion items */
}
```

#### AccordionItem Props

| Prop                                  | Type                          | Required |
| ------------------------------------- | ----------------------------- | -------- |
| [`as`](#accordionitem-as)             | `string` <Pipe /> `Component` | false    |
| [`children`](#accordionitem-children) | `node`                        | true     |
| [`disabled`](#accordionitem-disabled) | `boolean`                     | false    |

##### AccordionItem `as`

`as?: keyof JSX.IntrinsicElements | React.ComponentType`

A string representing an HTML element or a React component that will tell the `AccordionItem` what element to render. Defaults to `div`.

<AsPropWarning />

##### AccordionItem `children`

`children: React.ReactNode`

An `AccordionItem` expects to receive an `AccordionButton` and `AccordionPanel` components as its children, though you can also nest other components within an `AccordionItem` if you want some persistant content that is relevant to the section but not collapsible when the `AccordionButton` is toggled.

```jsx
<Accordion defaultIndex={[0, 1]} multiple>
	<AccordionItem>
		<AccordionButton>Step 1: Do this important thing</AccordionButton>
		<AccordionPanel>Detailed instructions about step 1.</AccordionPanel>
	</AccordionItem>
	<AccordionItem>
		<AccordionButton>Step 2: Do this other important thing</AccordionButton>
		<AccordionPanel>Detailed instructions about step 1.</AccordionPanel>
		{/* the following component will not collapse! */}
		<SomeCalloutBox>
			Important: you should always consult the user manual before doing step 2!
		</SomeCalloutBox>
	</AccordionItem>
</Accordion>
```

##### AccordionItem `disabled`

`disabled?: boolean`

Whether or not an accordion panel is disabled from user interaction. Defaults to `false`.

### AccordionButton

The trigger button a user clicks to interact with an accordion.

#### AccordionButton CSS Selectors

Please see the [styling guide](/styling).

```css
[data-reach-accordion-button] {
	/* styles for buttons in all accordion items */
}
[data-reach-accordion-button][aria-expanded] {
	/* styles for buttons in open accordion items */
}
[data-reach-accordion-button][disabled] {
	/* styles for all buttons in disabled accordion items */
}
```

#### AccordionButton Props

| Prop                                    | Type                          | Required |
| --------------------------------------- | ----------------------------- | -------- |
| [`as`](#accordionbutton-as)             | `string` <Pipe /> `Component` | false    |
| [`children`](#accordionbutton-children) | `node`                        | true     |

##### AccordionButton `as`

`as?: keyof JSX.IntrinsicElements | React.ComponentType`

A string representing an HTML element or a React component that will tell the `AccordionButton` what element to render. Defaults to `button`.

<AsPropWarning />

##### AccordionButton `children`

`children: React.ReactNode`

Typically a text string that serves as a label for the accordion, though nested DOM nodes can be passed as well so long as they are valid children of interactive elements.

If you need to group interactive elements within an accordion's button, we recommend grouping the button inside of a wrapper element rather than including invalid HTML inside the button:

```jsx
// jsx-demo
(() => {
	function Example() {
		return (
			<Accordion>
				<AccordionItem>
					<GroupedAccordionHeader>Option 1</GroupedAccordionHeader>
					<StyledAccordionPanel>
						Ante rhoncus facilisis iaculis nostra faucibus vehicula ac
						consectetur pretium, lacus nunc consequat id viverra facilisi ligula
						eleifend, congue gravida malesuada proin scelerisque luctus est
						convallis.
					</StyledAccordionPanel>
				</AccordionItem>
				<AccordionItem>
					<GroupedAccordionHeader>Option 2</GroupedAccordionHeader>
					<StyledAccordionPanel>
						Ante rhoncus facilisis iaculis nostra faucibus vehicula ac
						consectetur pretium, lacus nunc consequat id viverra facilisi ligula
						eleifend, congue gravida malesuada proin scelerisque luctus est
						convallis.
					</StyledAccordionPanel>
				</AccordionItem>
				<AccordionItem>
					<GroupedAccordionHeader>Option 3</GroupedAccordionHeader>
					<StyledAccordionPanel>
						Ante rhoncus facilisis iaculis nostra faucibus vehicula ac
						consectetur pretium, lacus nunc consequat id viverra facilisi ligula
						eleifend, congue gravida malesuada proin scelerisque luctus est
						convallis.
					</StyledAccordionPanel>
				</AccordionItem>
			</Accordion>
		);
	}

	function StyledAccordionPanel(props) {
		return <AccordionPanel style={{ padding: 10 }} {...props} />;
	}

	function GroupedAccordionHeader({ children }) {
		return (
			<div
				style={{
					alignItems: "center",
					background: "#eee",
					border: "1px solid #888",
					borderRadius: 3,
					margin: "9px 0",
					display: "flex",
					justifyContent: "space-between",
					padding: "4px 10px",
				}}
			>
				<AccordionButton
					style={{
						appearance: "none",
						background: 0,
						border: 0,
						boxShadow: "none",
						color: "inherit",
						display: "block",
						textAlign: "inherit",
						flexGrow: 1,
						flexShrink: 0,
						font: "inherit",
						fontWeight: "bolder",
						margin: 0,
						padding: "10px 0",
					}}
				>
					{children}
				</AccordionButton>
				<Menu>
					<MenuButton style={{ margin: 0 }}>
						<span>Actions</span>
					</MenuButton>
					<MenuList>
						<MenuItem onSelect={() => console.log("Download")}>
							Download
						</MenuItem>
						<MenuItem onSelect={() => console.log("Copy")}>
							Create a Copy
						</MenuItem>
					</MenuList>
				</Menu>
			</div>
		);
	}
	return <Example />;
})();
```

- **Further reading:** [_Be Wary of Nesting Roles_ by Adrian Roselli](https://adrianroselli.com/2016/12/be-wary-of-nesting-roles.html)

### AccordionPanel

The collapsible panel in which inner content for an accordion item is rendered.

#### AccordionPanel CSS Selectors

Please see the [styling guide](/styling).

```css
[data-reach-accordion-panel] {
	/* styles for all accordion panels */
}
[data-reach-accordion-panel][data-state="open"] {
	/* styles for all open accordion panels */
}
[data-reach-accordion-panel][data-state="collapsed"] {
	/* styles for all collapsed accordion panels */
}
[data-reach-accordion-panel][data-disabled] {
	/* styles for all disabled accordion panels */
}
```

#### AccordionPanel Props

| Prop                                   | Type                          | Required |
| -------------------------------------- | ----------------------------- | -------- |
| [`as`](#accordionpanel-as)             | `string` <Pipe /> `Component` | false    |
| [`children`](#accordionpanel-children) | `node`                        | true     |

##### AccordionPanel `as`

`as?: keyof JSX.IntrinsicElements | React.ComponentType`

A string representing an HTML element or a React component that will tell the `AccordionPanel` what element to render. Defaults to `div`.

<AsPropWarning />

##### AccordionPanel `children`

`children: React.ReactNode`

Inner collapsible content for the accordion item.

### useAccordionContext

`function useAccordionContext(): { id: string | undefined; openPanels: number[] }`

A hook that exposes data for a given `Accordion` component to its descendants.

### useAccordionItemContext

`function useAccordionItemContext(): { index: number; isExpanded: boolean }`

A hook that exposes data for a given `AccordionItem` component to its descendants.
